// Copyright 2009 Jan Hosang <jan.hosang@gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package depgraph

import (
    "log"
)

type Dependency interface {
    Build() bool
    Changed() bool
    Label() string
    Output() string
    Sources() []string
    Dependencies() []string
    Temporaries() []string
    String() string
}

type DependencyGraph map[string] *dependencyGraphElement
type dependencyGraphElement struct {
    obj Dependency
    visited bool
    updated bool
    deps []*dependencyGraphElement
}

func NewDependencyGraph() *DependencyGraph {
    graphObj := make(DependencyGraph)
    depGraph := &graphObj
    return depGraph
}

func (depGraph *DependencyGraph) addDependencyDummy(depName string) *dependencyGraphElement {
    id := depName
    
    // return a valid pointer even if we have no content in that graph element yet
    if dep, contained := (*depGraph)[id]; contained {
        return dep
    }
    
    dep := new(dependencyGraphElement)
    (*depGraph)[id] = dep
    return dep
}

func (depGraph *DependencyGraph) AddDependency(depObj Dependency) *dependencyGraphElement {
    id := depObj.Label()
    dep, contained := (*depGraph)[id]
    
    // here we actually fill content, no matter if the graph element was
    // inserted yet or not
    if !contained {
        dep = new(dependencyGraphElement)
        (*depGraph)[id] = dep
    }
    
    dep.obj = depObj
    depObjDeps := depObj.Dependencies()
    dep.deps = make([]*dependencyGraphElement, len(depObjDeps))
    // hook up all dependencies recursively
    for i, depName := range depObjDeps {
        dep.deps[i] = depGraph.addDependencyDummy(depName)
    }
    
    return dep
}


func (depGraph *DependencyGraph) Build(targets []string) {
    if len(targets) == 0 {
        // if no targets are specified, make all
        // all excludes "clean" and "nuke"
        for target, el := range (*depGraph) {
            if target != "clean" && target != "nuke" {
                el.build()
            }
        }
    } else {
        for _, trg := range targets {
            dep, found := (*depGraph)[trg]
            if found {
                dep.build()
            } else {
                log.Stderrf("could not find target \"%s\"", trg)
            }
        }
    }
}

func (self *dependencyGraphElement) build() bool {
    // make sure each node is only visited only once
    if !self.visited {
        self.visited = true
    
        // first build dependencies
        depChanged := false
        for _, dep := range self.deps {
            if dep.build() {
                depChanged = true
            }
        }
        
        self.updated = self.obj.Changed() || depChanged
        if self.updated {
            self.obj.Build()
        }
    }
    return self.updated
}

func (depGraph *DependencyGraph) String() string {
    result := ""
    for _, dep := range *depGraph {
        result += dep.obj.String()
    }
    return result
}

